package system_storage_service

import (
	"fmt"
	"io"
	"mime"
	"os"
	"path/filepath"
	"time"

	"gitee.com/Luna-CY/Golang-Project-Template/internal/service/system_storage_service/provider"

	"gitee.com/Luna-CY/Golang-Project-Template/internal/configuration"
	"gitee.com/Luna-CY/Golang-Project-Template/internal/context"
	"gitee.com/Luna-CY/Golang-Project-Template/internal/errors"
	"gitee.com/Luna-CY/Golang-Project-Template/internal/logger"
	"gitee.com/Luna-CY/Golang-Project-Template/internal/util/cryptor"
	"gitee.com/Luna-CY/Golang-Project-Template/internal/util/istrings"
	"gitee.com/Luna-CY/Golang-Project-Template/internal/util/pointer"
	"gitee.com/Luna-CY/Golang-Project-Template/model"
	gonanoid "github.com/matoous/go-nanoid"
)

func (cls *Storage) PutObject(ctx context.Context, path string, expired int64, async bool, file io.Reader) (*model.SystemObject, errors.Error) {
	if "" == path || nil == file {
		logger.SugarLogger(logger.WithRequestId(ctx), logger.WithStack()).Errorf("ISS_GE.S_GE.PO_CT.260020 filepath is %q file is %p", path, file)

		return nil, errors.ErrorServerInternalError("ISS_GE.S_GE.PO_CT.164451", "异常的传入参数")
	}

	if "" != configuration.Configuration.Storage.Prefix {
		path = filepath.Join(configuration.Configuration.Storage.Prefix, path)
	}

	// 取原始文件名
	dir, originalFilename := filepath.Split(path)
	path = filepath.Join(dir, fmt.Sprintf("%s%s", gonanoid.MustID(21), filepath.Ext(originalFilename)))

	tf, rerr := os.CreateTemp("", "")
	if rerr != nil {
		logger.SugarLogger(logger.WithRequestId(ctx), logger.WithStack()).Errorf("ISS_GE.S_GE.PO_CT.410039 create temp file failed: %v", rerr)

		return nil, errors.ErrorServerInternalError("ISS_GE.S_GE.PO_CT.235441", "创建临时文件失败")
	}

	size, rerr := io.Copy(tf, file)
	if rerr != nil {
		logger.SugarLogger(logger.WithRequestId(ctx), logger.WithStack()).Errorf("ISS_GE.S_GE.PO_CT.480052 copy file failed: %v", rerr)

		return nil, errors.ErrorServerInternalError("ISS_GE.S_GE.PO_CT.275454", "复制文件失败")
	}

	// 计算文件哈希值
	md5, sha1, sha256, err := cryptor.SumFile(tf)
	if err != nil {
		return nil, err.Relation(errors.ErrorServerInternalError("ISS_GE.S_GE.PO_CT.224846", "计算文件哈希值失败"))
	}

	if _, rerr := tf.Seek(0, io.SeekStart); rerr != nil {
		logger.SugarLogger(logger.WithRequestId(ctx), logger.WithStack()).Errorf("ISS_GE.S_GE.PO_CT.600110 seek temp file failed: %v", rerr)

		return nil, errors.ErrorServerInternalError("ISS_GE.S_GE.PO_CT.224846", "计算文件哈希值失败")
	}

	provider, err := cls.GetProvider()
	if err != nil {
		return nil, err.Relation(errors.ErrorServerInternalError("ISS_GE.S_GE.PO_CT.520014", "获取存储提供商失败"))
	}

	var object = new(model.SystemObject)
	object.Id = gonanoid.MustID(64)
	object.Provider = pointer.New(provider.GetName())
	object.Bucket = pointer.New(provider.GetBucket())
	object.OriginalFilename = pointer.New(originalFilename)
	object.Filepath = pointer.New(path)
	object.MimeType = pointer.New(mime.TypeByExtension(filepath.Ext(path)))
	object.Ext = pointer.New(filepath.Ext(path))
	object.Md5 = pointer.New(md5)
	object.Sha1 = pointer.New(sha1)
	object.Sha256 = pointer.New(sha256)
	object.Size = pointer.New(size)
	object.ReferCnt = pointer.New(int64(0))
	object.Status = pointer.New(model.SystemObjectStatusUploading)
	object.Progress = pointer.New(int64(0))

	if err := cls.object.SaveSystemObject(ctx, object); err != nil {
		return nil, err.Relation(errors.ErrorServerInternalError("ISS_GE.S_GE.PO_CT.760310", "保存对象失败"))
	}

	if async {
		go cls.putObject(ctx, object, provider, tf)
	} else {
		cls.putObject(ctx, object, provider, tf)
	}

	return object, nil
}

func (cls *Storage) putObject(ctx context.Context, object *model.SystemObject, provider provider.Provider, tf *os.File) {
	defer func() {
		if err := tf.Close(); err != nil {
			logger.SugarLogger(logger.WithRequestId(ctx), logger.WithStack()).Errorf("ISS_GE.S_GE.PO_CT.1030128 close temp file failed: %v", err)

			return
		}

		if err := os.Remove(tf.Name()); err != nil {
			logger.SugarLogger(logger.WithRequestId(ctx), logger.WithStack()).Errorf("OSS_GE.S_GE.PO_CT.1090142 remove temp file failed: %v", err)
		}
	}()

	var proxy = &proxy{
		Reader: tf,
	}

	go func() {
		for {
			if model.SystemObjectStatusUploaded == *object.Status {
				break
			}

			time.Sleep(time.Second * 10)

			object.Progress = pointer.New(int64(float64(proxy.cnt) / float64(*object.Size) * 100))

			if err := cls.object.SaveSystemObject(ctx, object); err != nil {
				logger.SugarLogger(logger.WithRequestId(ctx), logger.WithStack()).Errorf("ISS_GE.S_GE.PO_CT.1280206 update object failed: %v", err)
			}
		}
	}()

	if err := provider.PutObject(ctx, *object.Filepath, *object.Size, proxy); err != nil {
		object.Status = pointer.New(model.SystemObjectStatusFailed)
		object.FailedReason = pointer.New(istrings.MaxLen(err.Error(), 512))

		if err := cls.object.SaveSystemObject(ctx, object); err != nil {
			logger.SugarLogger(logger.WithRequestId(ctx), logger.WithStack()).Errorf("ISS_GE.S_GE.PO_CT.1380220 update object failed: %v", err)
		}
	}

	md5, sha1, sha256, err := provider.GetObjectHash(ctx, *object.Filepath)
	if err != nil {
		object.Status = pointer.New(model.SystemObjectStatusFailed)
		object.FailedReason = pointer.New(istrings.MaxLen(err.Error(), 512))

		if err := cls.object.SaveSystemObject(ctx, object); err != nil {
			logger.SugarLogger(logger.WithRequestId(ctx), logger.WithStack()).Errorf("ISS_GE.S_GE.PO_CT.1480358 update object failed: %v", err)
		}

		return
	}

	object.Status = pointer.New(model.SystemObjectStatusUploaded)
	object.Progress = pointer.New(int64(100))

	if "" != md5 && md5 != *object.Md5 {
		object.Status = pointer.New(model.SystemObjectStatusFailed)
	}

	if "" != sha1 && sha1 != *object.Sha1 {
		object.Status = pointer.New(model.SystemObjectStatusFailed)
	}

	if "" != sha256 && sha256 != *object.Sha256 {
		object.Status = pointer.New(model.SystemObjectStatusFailed)
	}

	if err := cls.object.SaveSystemObject(ctx, object); err != nil {
		logger.SugarLogger(logger.WithRequestId(ctx), logger.WithStack()).Errorf("ISS_GE.S_GE.PO_CT.1650235 update object failed: %v", err)
	}
}

type proxy struct {
	io.Reader

	cnt int64
}

func (cls *proxy) Read(p []byte) (n int, err error) {
	n, err = cls.Reader.Read(p)
	cls.cnt += int64(n)

	return
}
